package com.ch.mobilesha.img;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import android.content.Context;
import android.os.Environment;

import com.ch.mobilesha.common.CommonData;

/**
 * 包含内存缓存以及硬盘缓存
 * @author liuweixiang
 */
public abstract class AbstractCache<K, V> implements Map<K, V>{
	
	/** 缓存版本号 */
	static final int VERSION = 1;
	
	/** 缓存有效时间7天 */
	static final long EXPIR_TIME = 7 * 24 * 60 * 60 * 1000;
	
	/** 缓存在手机机身空间里  */
	public static final int DISK_CACHE_PHONE = 0;
	
	/** 缓存在sd卡  */
    public static final int DISK_CACHE_SDCARD = 1;
    
    /** 是否启用硬盘缓存  */
    private boolean diskCacheEnabled;
    
    /** 硬盘缓存目录路径  */
    private String diskCacheDirectory;
    
    /** 缓存具体目录名称  */
    private String dirName;
    
    /** 内存缓存  */
    protected HashMap<K, V> cache;
    
    /** 软应用缓存  */
    protected HashMap<K, SoftReference<V>> softCache;
    
    private boolean mUseSoftReference = false;
    
    public AbstractCache(String dirName){
    	this.dirName = dirName;
    	cache = new HashMap<K, V>();
    }
    
    public AbstractCache(String dirName, boolean useSoftReference){
    	mUseSoftReference = useSoftReference;
    	this.dirName = dirName;
    	softCache = new HashMap<K, SoftReference<V>>();
    }
    
    /**
     * 启用硬盘缓存
     * @param context
     * @param storageDevice	缓存位置  {@link #DISK_CACHE_PHONE}或者{@link #DISK_CACHE_SDCARD}.
     */
    public boolean enableDiskCache(Context context, int storageDevice){
    	Context appContext = context.getApplicationContext();
    	String rootDir = null;
    	if (storageDevice == DISK_CACHE_SDCARD && Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
    		// sd卡存在并且设置缓存在sd卡上
    		rootDir = Environment.getExternalStorageDirectory().getAbsolutePath() +  CommonData.CACHE_DIR_PATH;
    	} else if(storageDevice == DISK_CACHE_PHONE) {
            rootDir = appContext.getCacheDir().getAbsolutePath();
        }
    	
    	this.diskCacheDirectory = rootDir +	"/" + dirName;
    	File outFile = new File(diskCacheDirectory);
    	outFile.mkdirs();
    	diskCacheEnabled = outFile.exists();
    	return diskCacheEnabled;
    }
    
    /**
     * 放到内存缓存时是否自动保存到SD卡
     * @return
     */
    protected boolean isAuto2Disk(){
    	if(diskCacheEnabled){
    		return auto2Disk();
    	}
    	return false;
    }
    
    public boolean auto2Disk(){
    	return true;
    }
    
    /**
     * 获取缓存目录
     * @return
     */
    public String getDiskCacheDirectory() {
        return diskCacheDirectory;
    }
    
    /**
     * 从缓存key获取文件名，仅在硬盘缓存启用的时候有效
     * @param key
     * @return
     */
    public abstract String getFileNameForKey(K key);
    
    /**
     * 根据缓存key从硬盘缓存里获取数据，仅在硬盘缓存启用的时候有效
     * @param file
     * @return
     * @throws IOException
     */
    protected abstract V readCacheFromDisk(K key) throws IOException;
    
    
    /**
     * 数据写入缓存的具体实现，子类实现
     * @param ostream
     * @param value
     * @throws IOException
     */
    protected abstract void writeValueToDisk(ObjectOutputStream ostream, V value) throws IOException;
    
    /**
     * 将数据写入硬盘缓存
     * @param ostream
     * @param value
     * @throws IOException
     */
    public synchronized void cacheToDisk(K key, V value){
    	File file = new File(diskCacheDirectory + "/" + getFileNameForKey(key));
    	try {
            file.createNewFile();
            file.deleteOnExit();

            ObjectOutputStream ostream = new ObjectOutputStream(new FileOutputStream(file));

            writeValueToDisk(ostream, value);

            ostream.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    protected File getFileForKey(K key) {
        return new File(diskCacheDirectory + "/" + getFileNameForKey(key));
    }
    
    @SuppressWarnings("unchecked")
	public synchronized V get(Object elementKey) {
		K key = (K)elementKey;
		V value = null;
		if(mUseSoftReference){
			SoftReference<V> svalue = softCache.get(key);
			if(svalue != null && svalue.get() != null){
				// 从内存缓存中取到数据，直接返回
				return svalue.get();
			}
		}else{
			value = cache.get(key);
			if(value != null){
//				WLog.d(TAG, "Memory cache hit for " + key.toString());
				return value;
			}
		}
		// 内存缓存中没有取到，尝试从硬盘缓存读取
		try {
			value = readCacheFromDisk(key);
		} catch (IOException e) {
			e.printStackTrace();
		}
		if(value != null){
			// 从硬盘缓存中读到，那么加入到内存缓存中下次再读的时候不需要再去硬盘缓存读，提高效率
			if(mUseSoftReference){
				softCache.put(key, new SoftReference<V>(value));
			}else{
				cache.put(key, value);
			}
		}
		return value;
	}
    
	public synchronized V put(K key, V value) {
    	clearDiskCachIfNeeded();
    	if(mUseSoftReference){
    		softCache.put(key, new SoftReference<V>(value));
    	}else{
    		cache.put(key, value);
    	}
    	if(isAuto2Disk()){
    		cacheToDisk(key, value);
    	}
    	return value;
	}
    
	public synchronized void putAll(Map<? extends K, ? extends V> map) {
    	throw new UnsupportedOperationException("Not support now");
	}
    
    /**
     * 查询内存缓存是否有对应的
     * @param key
     * @return
     */
	public synchronized boolean containsKey(Object key) {
    	if(mUseSoftReference){
    		return softCache.containsKey(key) && softCache.get(key).get() != null;
    	}else{
    		return cache.containsKey(key);
    	}
	}
    
//    public synchronized boolean containsKeyInMemory(Object key) {
//    	if(mUseSoftReference){
//    		return softCache.containsKey(key) && softCache.get(key).get() != null;
//    	}else{
//    		return cache.containsKey(key);
//    	}
//    }
    
    /**
     * 查看内存缓存中是否有这个对象
     */
    public synchronized boolean containsValue(Object value) {
        return cache.containsValue(value);
    }
    
    @SuppressWarnings("unchecked")
	public synchronized V remove(Object key) {
    	
    	V value = removeMemoryCache(key);

        if (diskCacheEnabled) {
            File cachedValue = getFileForKey((K) key);
            if (cachedValue.exists()) {
                cachedValue.delete();
            }
        }
        return value;
	}
    
    public synchronized V removeMemoryCache(Object key) {
    	if(mUseSoftReference){
			SoftReference<V> sr = softCache.remove(key);
			return sr.get();
		}else{
			return cache.remove(key);
		}
	}
    
	public Set<K> keySet() {
		return cache.keySet();
	}
    
	public Set<java.util.Map.Entry<K, V>> entrySet() {
    	throw new UnsupportedOperationException("Not support now");
	}
    
	public int size() {
		return cache.size();
	}
    
	public boolean isEmpty() {
		return cache.isEmpty();
	}
    
	public void clear() {
		if(mUseSoftReference){
			softCache.clear();
		}else{
			cache.clear();
		}

        if (diskCacheEnabled) {
            File[] cachedFiles = new File(diskCacheDirectory).listFiles();
            if (cachedFiles == null) {
                return;
            }
            for (File f : cachedFiles) {
                f.delete();
            }
        }
	}
	
	public void clearMemoryCache(){
		if(mUseSoftReference){
			softCache.clear();
		}else{
			cache.clear();
		}
	}

	public Collection<V> values() {
		throw new UnsupportedOperationException("Not support now");
	}
	
	private void clearDiskCachIfNeeded(){
		if(!diskCacheEnabled){
			return;
		}
		
	}

}
